<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>错误码文档系统</title>
    <style>
        :root {
            --primary-color: #409EFF;
            --hover-color: #79BBFF;
            --border-color: #EBEEF5;
            --bg-color: #F8F9FA;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            margin: 20px;
            color: #606266;
        }

        #search {
            width: 100%;
            max-width: 400px;
            padding: 10px 15px;
            border: 1px solid var(--border-color);
            border-radius: 4px;
            margin-bottom: 20px;
            transition: box-shadow 0.3s;
        }

        #search:focus {
            outline: none;
            box-shadow: 0 0 5px rgba(64, 158, 255, 0.3);
        }

        .data-table {
            width: 100%;
            border-collapse: collapse;
            box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
            background: white;
        }

        .data-table th {
            background: var(--bg-color);
            padding: 12px 15px;
            text-align: left;
            font-weight: 500;
            border-bottom: 2px solid var(--border-color);
        }

        .data-table td {
            padding: 12px 15px;
            border-bottom: 1px solid var(--border-color);
        }

        .data-table tr:hover {
            background-color: rgba(64, 158, 255, 0.05);
        }

        .tree-arrow {
            cursor: pointer;
            display: inline-block;
            width: 18px;
            height: 18px;
            text-align: center;
            margin-right: 5px;
            transition: transform 0.2s;
        }

        .tree-arrow.expanded {
            transform: rotate(90deg);
        }

        .pagination {
            margin-top: 20px;
            display: flex;
            gap: 15px;
            align-items: center;
        }

        .pagination button {
            padding: 8px 16px;
            border-radius: 4px;
            border: 1px solid var(--border-color);
            background: white;
            color: var(--primary-color);
            cursor: pointer;
        }

        .pagination button:disabled {
            color: #C0C4CC;
            cursor: not-allowed;
        }

        .pagination button:hover:not(:disabled) {
            background: var(--primary-color);
            color: white;
        }

        .level-indent {
            padding-left: 30px;
        }

        .level-indent.level-1 {
            padding-left: 45px;
        }

        .level-indent.level-2 {
            padding-left: 60px;
        }

        .empty-tip {
            text-align: center;
            color: #909399;
            padding: 40px 0;
        }

        .code-segment {
            color: #666;
            margin-right: 8px;
        }

        .tree-indent {
            display: inline-block;
            width: 24px;
            vertical-align: top;
        }

        .level-indent {
            padding-left: 30px;
        }

        .level-indent.level-1 {
            padding-left: 54px;
        }

        .level-indent.level-2 {
            padding-left: 78px;
        }

        .level-indent.level-3 {
            padding-left: 102px;
        }

        /* 调整箭头位置 */
        .tree-arrow {
            vertical-align: top;
            margin-right: 8px;
        }
    </style>
</head>
<body>
<input type="text" id="search" placeholder="🔍 输入错误码、描述或类名进行搜索...">
<div id="data-container"></div>
<div id="pagination"></div>

<script>
    // 原始数据（示例）
    const allData = [
        {"code": "AL0000", "className": "com.example.OrderError", "message": "订单创建失败", "userTip": "请检查商品库存"},
        {"code": "AL0001", "className": "com.example.OrderError", "message": "订单创建", "userTip": ""},
        {"code": "BP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "CP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "DP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "EP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "FP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "GP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "HP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "JP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "KP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "LP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "MP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "NP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "OP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "PP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "QP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "RP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "SP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "TP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "UP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "VP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "WP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "XP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "YP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
        {"code": "ZP0101", "className": "com.example.PaymentError", "message": "支付超时", "userTip": "请重新发起支付"},
    ];

    // 层级配置（示例）
    const hierarchyConfig = [
        {name: "系统", length: 1, mapping: {"A": "订单系统", "B": "支付系统"}},
        {name: "模块", length: 2, mapping: {"L0": "核心模块"}},
        {name: "子模块", length: 2, mapping: {"00": "子模块A", "10": "子模块B"}},
        {name: "错误类型", length: 1, mapping: null}
    ];

    const state = {
        pageSize: 10,
        currentPage: 1,
        expandedCodes: new Set(),
        flatData: [],
        currentKeyword: '',
    };

    // 初始化
    function init() {
        const treeData = buildHierarchyTree(allData);
        state.flatData = flattenTree(treeData);
        render();
        setupEventListeners();
    }

    function validateHierarchyConfig() {
        if (!hierarchyConfig.length) return false;

        // 检查所有错误码长度是否一致
        const codeLengths = new Set(allData.map(d => d.code.length));
        if (codeLengths.size !== 1) return false;

        // 计算配置总长度
        const totalConfigLength = hierarchyConfig.reduce((sum, c) => sum + c.length, 0);
        return Array.from(codeLengths)[0] === totalConfigLength;
    }

    // 构建层级树（关键修改部分）
    function buildHierarchyTree(data) {

        // 无配置或校验失败时直接返回叶子节点
        if (!validateHierarchyConfig()) {
            return data.map(item => ({
                ...wrapLeafNode(item),
                depth: 0,
                path: item.code,
                isLeaf: true
            }));
        }

        if (!hierarchyConfig.length) return data.map(wrapLeafNode);

        const root = {children: []};

        data.forEach(item => {
            let code = item.code;
            let currentNode = root;
            const pathSegments = [];

            hierarchyConfig.forEach((config, depth) => {
                // 判断是否是最后一个层级
                const isLastLevel = depth === hierarchyConfig.length - 1;

                // 如果是最后一个层级，直接创建叶子节点
                if (isLastLevel) {
                    const leafNode = {
                        ...wrapLeafNode(item),
                        seg: code, // 保留完整剩余code
                        path: pathSegments.map(p => p.seg).join('') + code,
                        depth: depth + 1,
                        isLeaf: true,
                        originalData: item
                    };
                    currentNode.children.push(leafNode);
                    return; // 跳过后续处理
                }

                // 正常处理非最后层级
                const seg = code.substring(0, config.length);
                code = code.substring(config.length);

                const display = config.mapping?.[seg] || seg;
                pathSegments.push({seg, display, depth, configName: config.name});

                let child = currentNode.children.find(c => c.seg === seg);
                if (!child) {
                    child = {
                        seg,
                        display,
                        depth,
                        configName: config.name,
                        path: pathSegments.map(p => p.seg).join(''),
                        children: [],
                        isLeaf: false,
                        originalData: null
                    };
                    currentNode.children.push(child);
                }
                currentNode = child;
            });
        });

        return root.children;
    }

    function wrapLeafNode(item) {
        return {
            seg: item.code,
            display: item.code,
            depth: hierarchyConfig.length,
            configName: "具体错误",
            path: item.code,
            originalData: item,
            isLeaf: true,
            children: []
        };
    }

    // 扁平化树结构
    function flattenTree(nodes, level = 0) {
        // 无层级配置时直接返回
        // if (!validateHierarchyConfig()) {
        //     return nodes.map(n => ({ ...n, level: 0 }));
        // }

        return nodes.reduce((acc, node) => {
            acc.push({...node, level});

            if (node.children && state.expandedCodes.has(node.path)) {
                acc.push(...flattenTree(node.children, level + 1));
            }
            return acc;
        }, []);
    }

    // 渲染主界面
    function render() {
        const start = (state.currentPage - 1) * state.pageSize;
        const pageData = state.flatData.slice(start, start + state.pageSize);

        const tableHtml = `
        <table class="data-table">
            <thead>
                <tr>
                    <th>错误码</th>
                    <th>描述</th>
                    <th>用户提示</th>
                    <th>所属类</th>
                </tr>
            </thead>
            <tbody>
                ${pageData.map(item => `
                    <tr class="${getIndentClass(item)}">
                        <td>${renderCodeColumn(item)}</td>
                        <td>${renderDescription(item)}</td>
                        <td>${renderUserTip(item)}</td>
                        <td>${renderClassName(item)}</td>
                    </tr>
                `).join('')}
            </tbody>
        </table>
    `;

        document.getElementById('data-container').innerHTML =
            state.flatData.length ? tableHtml : '<div class="empty-tip">没有找到匹配的结果</div>';

        renderPagination();
    }

    // 新增缩进class计算
    function getIndentClass(item) {
        const depth = calculateNodeDepth(item);
        return depth > 0 ? `level-indent level-${depth - 1}` : '';
    }

    function renderCodeColumn(item) {
        if (!hierarchyConfig.length || !validateHierarchyConfig()) {
            return item.originalData.code;
        }

        // 计算实际显示层级（关键修改）
        const depth = calculateNodeDepth(item);
        let indent = '';
        for (let i = 0; i < depth; i++) {
            indent += `<span class="tree-indent"></span>`;
        }

        let codeParts = [];

        // 添加展开箭头
        if (item.children?.length) {
            const isExpanded = state.expandedCodes.has(item.path);
            codeParts.push(`
                <span class="tree-arrow ${isExpanded ? 'expanded' : ''}"
                      onclick="toggleExpand('${item.path}', event)">▶</span>
            `);
        }

        // 显示内容
        if (!item.isLeaf) {
            codeParts.push(`<span class="code-segment">${item.seg}</span>`);
        } else {
            codeParts.push(item.path);
        }

        return `${indent}${codeParts.join('')}`;
    }

    // 新增节点深度计算方法
    function calculateNodeDepth(node) {
        if (!node.path || !hierarchyConfig) return 0;

        // 通过path长度计算实际层级
        let currentLength = 0;
        let depth = 0;
        hierarchyConfig.some(config => {
            currentLength += config.length;
            depth++;
            return currentLength >= node.path.length;
        });

        return depth - 1; // 返回实际树层级
    }

    function renderDescription(item) {
        if (item.isLeaf) {
            return item.originalData.message || '-';
        }
        // 修改此处：显示mapping值 + 层级名称
        return `${item.display}（${item.configName}）`;
    }

    function renderUserTip(item) {
        return item.isLeaf ? (item.originalData.userTip || '-') : '-';
    }

    function renderClassName(item) {
        return item.isLeaf ? item.originalData.className : '-';
    }

    // 分页控制
    function renderPagination() {
        const totalPages = Math.ceil(state.flatData.length / state.pageSize);
        document.getElementById('pagination').innerHTML = `
        <div class="pagination">
            <button ${state.currentPage === 1 ? 'disabled' : ''}
                onclick="changePage(${state.currentPage - 1})">上一页</button>
            <span>第 ${state.currentPage} 页 / 共 ${totalPages} 页</span>
            <button ${state.currentPage >= totalPages ? 'disabled' : ''}
                onclick="changePage(${state.currentPage + 1})">下一页</button>
        </div>
    `;
    }

    // 事件处理
    function toggleExpand(path, event) {
        event.stopPropagation();
        state.expandedCodes.has(path)
            ? state.expandedCodes.delete(path)
            : state.expandedCodes.add(path);

        refreshData();
    }

    // 新增数据刷新方法
    function refreshData() {
        const allNodes = buildHierarchyTree(allData);

        if (state.currentKeyword) {
            const {matchPaths} = findMatchPaths(allNodes, state.currentKeyword);
            state.flatData = filterAndFlatten(allNodes, matchPaths);
        } else {
            state.flatData = flattenTree(allNodes);
        }

        render();
    }

    function changePage(newPage) {
        state.currentPage = Math.max(1,
            Math.min(newPage, Math.ceil(state.flatData.length / state.pageSize)));
        render();
    }

    function handleSearch() {
        const keyword = document.getElementById('search').value.toLowerCase();
        state.currentKeyword = keyword;

        const allNodes = buildHierarchyTree(allData);
        const {matchPaths, expandPaths} = findMatchPaths(allNodes, keyword);

        // 自动展开匹配路径的父级
        expandPaths.forEach(p => state.expandedCodes.add(p));

        state.flatData = keyword
            ? filterAndFlatten(allNodes, matchPaths)
            : flattenTree(allNodes);

        state.currentPage = 1;
        render();
    }

    // 整合后的过滤扁平化方法
    function filterAndFlatten(nodes, matchPaths) {
        return nodes.reduce((acc, node) => {
            const shouldShow = Array.from(matchPaths).some(p =>
                p === node.path || p.startsWith(node.path)
            );

            if (shouldShow) {
                acc.push(node);

                // 始终展开匹配路径的父级
                if (node.children && state.expandedCodes.has(node.path)) {
                    acc.push(...filterAndFlatten(node.children, matchPaths));
                }
            }
            return acc;
        }, []);
    }

    // 查找匹配路径的方法
    function findMatchPaths(nodes, keyword) {
        const matchPaths = new Set();
        const expandPaths = new Set();

        function search(nodes) {
            return nodes.some(node => {
                let match = false;
                if (node.isLeaf) {
                    match = ['code', 'message', 'className'].some(
                        field => node.originalData[field].toLowerCase().includes(keyword)
                    );
                } else {
                    match = ['display', 'configName'].some(
                        field => (node[field] || '').toLowerCase().includes(keyword)
                    );
                }

                const childMatch = node.children ? search(node.children) : false;

                if (match || childMatch) {
                    matchPaths.add(node.path);
                    if (childMatch) expandPaths.add(node.path);
                }

                return match || childMatch;
            });
        }

        search(nodes);
        return {matchPaths, expandPaths};
    }

    // 带过滤条件的扁平化方法
    function flattenTreeWithFilter(nodes, matchPaths, level = 0, parentVisible = true) {
        return nodes.reduce((acc, node) => {
            const shouldShow = Array.from(matchPaths).some(p =>
                p === node.path || p.startsWith(node.path)
            );

            const shouldExpand = state.expandedCodes.has(node.path) ||
                state.forceExpandPaths.has(node.path);

            if (shouldShow) {
                acc.push({...node, level});

                if (node.children && shouldExpand) {
                    acc.push(...flattenTreeWithFilter(node.children, matchPaths, level + 1));
                }
            }
            return acc;
        }, []);
    }

    function setupEventListeners() {
        document.getElementById('search').addEventListener('input',
            debounce(handleSearch, 300));
    }

    function debounce(fn, delay) {
        let timer;
        return (...args) => {
            clearTimeout(timer);
            timer = setTimeout(() => fn(...args), delay);
        };
    }

    // 启动应用
    document.addEventListener('DOMContentLoaded', init);
</script>
</body>
</html>
